6.OpenGL ES绘制矩形及圆形

写完上面的绘制三角形的部分,手快抽筋了,为啥?
一个就是每个方法都要调用GLES30.还有一个就是我们完全可以把公共的代码再封装到一个Base类里面啊,所以我这里就抽了一个BaseGLSurfaceViewRenderer类,然后把GLES30中一些常用的方法写了一遍。
为什么要这样做呢? 我们也可以通过import static来省去GLES30的写法,但我不喜欢那样。另外还弄了BufferUtil、ProjectionMatrixUtil的类:

public class BufferUtil {
    private static final int BYTES_PER_FLOAT = 4;

    public static FloatBuffer getFloatBuffer(float[] array) {
        FloatBuffer floatBuffer = ByteBuffer.allocateDirect(array.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        floatBuffer.put(array);
        floatBuffer.position(0);
        return floatBuffer;
    }
}
public class ProjectionMatrixUtil {
    // 矩阵数组
    private static final float[] mProjectionMatrix = new float[]{
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1,
    };

    public static void orthoM(int program, int width, int height, String name) {
        int uMatrixLocation = GLES30.glGetUniformLocation(program, name);
        //计算宽高比 边长比(>=1),非宽高比
        float aspectRatio = width > height ?
                (float) width / (float) height :
                (float) height / (float) width;
        if (width > height) {
            // 横屏
            Matrix.orthoM(mProjectionMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
        } else {
            // 竖屏or正方形
            Matrix.orthoM(mProjectionMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
        }
        GLES30.glUniformMatrix4fv(uMatrixLocation, 1, false, mProjectionMatrix, 0);
    }
}
public abstract class BaseGLSurfaceViewRenderer implements GLSurfaceView.Renderer {
    protected int mProgram;

    /**
     * readResource -> compileShader -> linkProgram -> useProgram
     *
     * @param context
     * @param vertexShader
     * @param fragmentShader
     */
    protected void handleProgram(@NonNull Context context, @RawRes int vertexShader, @RawRes int fragmentShader) {
        String vertexShaderStr = ResReadUtils.readResource(context, vertexShader);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(context, fragmentShader);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //在OpenGLES环境中使用程序
        GLES30.glUseProgram(mProgram);
    }

    protected void glViewport(int x, int y, int width, int height) {
        GLES30.glViewport(x, y, width, height);
    }

    protected void glClearColor(float red, float green, float blue, float alpha) {
        GLES30.glClearColor(red, green, blue, alpha);
    }

    protected void glClear(int mask) {
        GLES30.glClear(mask);
    }

    protected static void glEnableVertexAttribArray(int index) {
        GLES30.glEnableVertexAttribArray(index);
    }

    protected void glDisableVertexAttribArray(int index) {
        GLES30.glDisableVertexAttribArray(index);
    }

    protected int glGetAttribLocation(String name) {
        return GLES30.glGetAttribLocation(mProgram, name);
    }

    protected int glGetUniformLocation(String name) {
        return GLES30.glGetUniformLocation(mProgram, name);
    }

    protected void glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset) {
        GLES30.glUniformMatrix4fv(location, count, transpose, value, offset);
    }

    protected void glDrawArrays(int mode, int first, int count) {
        GLES30.glDrawArrays(mode, first, count);
    }

    protected void glDrawElements(int mode, int count, int type, int offset) {
        GLES30.glDrawElements(mode, count, type, offset);
    }

    protected void orthoM(String name, int width, int height) {
        ProjectionMatrixUtil.orthoM(mProgram, width, height, name);
    }

    protected void glVertexAttribPointer(
            int indx,
            int size,
            int type,
            boolean normalized,
            int stride,
            java.nio.Buffer ptr) {
        GLES30.glVertexAttribPointer(indx, size, type, normalized, stride, ptr);
    }
}

前面绘制点、线、三角形的时候在用GLES30.glDrawArrays(GL_TRIANGLE_STRIP)方法时选择的mode是GL_TRIANGLE_STRIP,这个mode还有其他的类型,假设我现在想要绘制一个6边形呢,这里以A、B、C、D、E、F六个点来说一下mode的区别:

  • GL_POINTS : 绘制独立的点。
  • GL_LINES : 绘制每两个点的一条线。AB、CD、EF
  • GL_LINE_LOOP : 按顺序将所有的点都连接起来,包括收尾相连。AB、BC、CD、DE、EF、FA
  • GL_LINE_STRIP:按顺序将所有的点连接起来,不包括收尾相连。AB、BC、CD、DE、EF
  • GL_TRIANGLES:每3个点构成一个三角形。 ABC、DEF
  • GL_TRIANGLES_STRIP:相邻3个点构成一个三角形,不包括收尾两个点。ABC、BCD、CDE、DEF
  • GL_TRIANGLE_FAN:第一个点和之后所有相邻的两个点构成一个三角形。ABC、ACD、ADE、AEF

OpenGL ES中仅允许采用三角形来搭建物体。其实这从构造能力上来说并没有区别,因为任何多边形都可以拆分为多个三角形。OpenGL ES中之所以仅支持三角形而不支持任意多边形是出于性能的考虑,就目前移动嵌入式设备的硬件性能情况来看,这是必然的选择了。

顶点法和索引法

  • 顶点法

    上一篇文章中写的绘制点、线、三角形都是使用GLES30.glDrawArrays()来绘制,它是顶点法。根据传入的顶点顺序进行绘制。顶点复用情况少,可读性低。

  • 索引法

    根据索引序列,在顶点序列中找到对应的顶点,并根据绘制的方式,组成相应的图元绘制,用的是GLES30.glDrawElements(),称为索引法。
    相对于顶点法在复杂图形的绘制中无法避免大量顶点重复的情况,索引法可以减少很多重复顶点占用的空间,所以复杂的图形下推荐使用索引法。顶点复用情况多,可读性高。

之前说过OpenGL ES提供的的图元单位是三角形,想要绘制其他多边形,就要利用三角形来拼成。 矩形是两个三角形,而圆形则是由很多个三角形组成,个数越多,圆越圆。

元素缓冲对象(EBO)

在渲染顶点这一话题上我们还有最后一个需要讨论的东西——元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)。

要解释元素缓冲对象的工作方式最好还是举个例子:假设我们不再绘制一个三角形而是绘制一个矩形。
我们可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:

float vertices[] = {
    // 第一个三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二个三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

可以看到,有几个顶点叠加了。
我们指定了右下角和左上角两次!

一个矩形只有4个而不是6个顶点,这样就产生50%的额外开销。
当我们有包括上千个三角形的模型之后这个问题会更糟糕,这会产生一大堆浪费。

更好的解决方案是只储存不同的顶点,并设定绘制这些顶点的顺序。这样子我们只要储存4个顶点就能绘制矩形了,之后只要指定绘制的顺序就行了。如果OpenGL提供这个功能就好了,对吧?

值得庆幸的是,元素缓冲区对象的工作方式正是如此。 EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。
这种所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

int indices[] = {
    // 注意索引从0开始! 
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};

你可以看到,当使用索引的时候,我们只定义了4个顶点,而不是6个。下一步我们需要创建元素缓冲对象:

//创建 ebo
GLES30.glGenBuffers(1,ebo[0])
//绑定 ebo 到上下文
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ebo[0])
//设置数据
GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,
    indexData.capacity() * 4,
    indexData,
    GLES30.GL_STATIC_DRAW
)

与VBO类似,我们先绑定EBO然后用glBufferData把索引复制到缓冲里。
同样,和VBO类似,我们会把这些函数调用放在绑定和解绑函数调用之间,只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
注意:我们传递了GL_ELEMENT_ARRAY_BUFFER当作缓冲目标。最后一件要做的事是用glDrawElements来替换glDrawArrays函数,表示我们要从索引缓冲区渲染三角形。
使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:

GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6,GLES30.GL_UNSIGNED_INT, 0)
  • 第一个参数指定了我们绘制的模式,这个和glDrawArrays的一样。
  • 第二个参数是我们打算绘制顶点的个数,这里填6,也就是说我们一共需要绘制6个顶点。
  • 第三个参数是索引的类型,这里是GL_UNSIGNED_INT。
  • 最后一个参数里我们可以指定EBO中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。

glDrawElements函数从当前绑定到GL_ELEMENT_ARRAY_BUFFER目标的EBO中获取其索引。
这意味着我们每次想要使用索引渲染对象时都必须绑定相应的EBO,这又有点麻烦。
碰巧顶点数组对象也跟踪元素缓冲区对象绑定。在绑定VAO时,绑定的最后一个元素缓冲区对象存储为VAO的元素缓冲区对象。然后,绑定到VAO也会自动绑定该EBO。

image

注意: EBO的绑定一定要在VAO解绑之前绑定!
当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
OpenGL中会解绑VBO和VAO但是不能解绑EBO)

到这里我有点懵了,VAO有什么用?

简单说就是VBO用于存储顶点数据,而VAO用于封装顶点数组的状态,包括顶点属性指针和VBO的绑定状态。这两者的结合使用可以使OpenGL中管理和切换多个顶点数据配置变得更加方便和高效。

绘制矩形

  • 顶点着色器与上一个三角形的一样

  • 片段着色器与上一个三角形的一样

  • Render的实现如下

    顶点法:

    public class SquareRender extends BaseGLSurfaceViewRenderer {
        //顶点位置缓存
        private final FloatBuffer vertexBuffer;
        //顶点颜色缓存
        private final FloatBuffer colorBuffer;
        //位置
        private int aPositionLocation;
        //颜色
        private int aColorLocation;
    
        /**
         * 坐标占用的向量个数
         */
        private static final int POSITION_COMPONENT_COUNT = 2;
        private static final float[] POINT_DATA = {
                -0.5f, -0.5f,
                0.5f, -0.5f,
                -0.5f, 0.5f,
                0.5f, 0.5f,
        };
    
        /**
         * 颜色占用的向量个数
         */
        private static final int COLOR_COMPONENT_COUNT = 4;
        private static final float[] COLOR_DATA = {
                // 一个顶点有3个向量数据:r、g、b、a
                1f, 0.5f, 0.5f, 0f,
                1f, 0f, 1f, 0f,
                0f, 1f, 1f, 0f,
                1f, 1f, 0f, 0f
        };
    
        public SquareRender() {
            vertexBuffer = BufferUtil.getFloatBuffer(POINT_DATA);
            colorBuffer = BufferUtil.getFloatBuffer(COLOR_DATA);
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            handleProgram(MyApplication.getInstance(), R.raw.iso_triangle_vertex_shader, R.raw.iso_triangle_fragment_shader);
            aPositionLocation = glGetAttribLocation("vPosition");
            aColorLocation = glGetAttribLocation("aColor");
            glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertexBuffer);
            glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, colorBuffer);
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            glViewport(0, 0, width, height);
            orthoM("u_Matrix", width, height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            glClear(GLES30.GL_COLOR_BUFFER_BIT);
            glEnableVertexAttribArray(aPositionLocation);
            glEnableVertexAttribArray(aColorLocation);
            // 正方形、四个点(POINT_DATA.length / POSITION_COMPONENT_COUNT)
            glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, POINT_DATA.length / POSITION_COMPONENT_COUNT);
            glDisableVertexAttribArray(aPositionLocation);
            glDisableVertexAttribArray(aColorLocation);
        }
    }
    

    上面是一个正方形,那如果我画一个六边形,就需要多定义好几个顶点数据,来让其能组成对应的三角形拼接,下面是用索引法来画一个六边形,索引法相对于顶点法来说可以更高效,能节省很多顶点的数据:

    public class HexagonRender extends BaseGLSurfaceViewRenderer {
        //顶点位置缓存
        private final FloatBuffer vertexBuffer;
        //顶点颜色缓存
        private final FloatBuffer colorBuffer;
        // 顶点索引缓存
        private final ShortBuffer indexBuffer;
        //位置
        private int aPositionLocation;
        //颜色
        private int aColorLocation;
    
        /**
         * 坐标占用的向量个数
         */
        private static final int POSITION_COMPONENT_COUNT = 2;
        private static final float[] POINT_DATA = {
                -0.5f, -0.5f,
                0.5f, -0.5f,
                0.5f, 0.5f,
                -0.5f, 0.5f,
                0f, -1.0f,
                0f, 1.0f
        };
    
        /**
         * 数组绘制的索引:当前是绘制三角形,所以是3个元素构成一个绘制顺序
         */
        private static final short[] INDEX_DATA = {
                0, 1, 2,
                0, 2, 3,
                0, 4, 1,
                3, 2, 5
        };
    
        /**
         * 颜色占用的向量个数
         */
        private static final int COLOR_COMPONENT_COUNT = 4;
        private static final float[] COLOR_DATA = {
                // 一个顶点有3个向量数据:r、g、b、a
                1f, 0.5f, 0.5f, 0f,
                1f, 0f, 1f, 0f,
                0f, 1f, 1f, 0f,
                1f, 1f, 0f, 0f
        };
    
        public HexagonRender() {
            vertexBuffer = BufferUtil.getFloatBuffer(POINT_DATA);
            colorBuffer = BufferUtil.getFloatBuffer(COLOR_DATA);
            indexBuffer = BufferUtil.getShortBuffer(INDEX_DATA);
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            handleProgram(MyApplication.getInstance(), R.raw.iso_triangle_vertex_shader, R.raw.iso_triangle_fragment_shader);
            aPositionLocation = glGetAttribLocation("vPosition");
            aColorLocation = glGetAttribLocation("aColor");
            glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertexBuffer);
            glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, colorBuffer);
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            glViewport(0, 0, width, height);
            orthoM("u_Matrix", width, height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            glClear(GLES30.GL_COLOR_BUFFER_BIT);
            glEnableVertexAttribArray(aPositionLocation);
            glEnableVertexAttribArray(aColorLocation);
            // 绘制相对复杂的图形时,若顶点有较多重复时,对比数据占用空间而言,glDrawElements会比glDrawArrays小很多,也会更高效
            // 因为在有重复顶点的情况下,glDrawArrays方式需要的3个顶点位置是用Float型的,占3*4的Byte值;
            // 而glDrawElements需要3个Short型的,占3*2Byte值
            // 1. 图形绘制方式; 2. 绘制的顶点数; 3. 索引的数据格式; 4. 索引的数据Buffer
            glDrawElements(GLES30.GL_TRIANGLES, INDEX_DATA.length,
                    GLES30.GL_UNSIGNED_SHORT, indexBuffer);
            glDisableVertexAttribArray(aPositionLocation);
            glDisableVertexAttribArray(aColorLocation);
        }
    }
    

绘制圆形

其他也都和上面的一样,只有Render不同,如下:

public class CircleRender extends BaseGLSurfaceViewRenderer {
    //顶点位置缓存
    private final FloatBuffer vertexBuffer;
    //顶点颜色缓存
    private final FloatBuffer colorBuffer;
    //位置
    private int aPositionLocation;
    //颜色
    private int aColorLocation;

    /**
     * 坐标占用的向量个数
     */
    private static final int POSITION_COMPONENT_COUNT = 3;
    private float circlePosition[];
    /**
     * 颜色占用的向量个数
     */
    private static final int COLOR_COMPONENT_COUNT = 4;
    private float color[];

    public CircleRender() {
        createPositions(1, 60);
        vertexBuffer = BufferUtil.getFloatBuffer(circlePosition);
        colorBuffer = BufferUtil.getFloatBuffer(color);
    }

    private void createPositions(int radius, int n){
        ArrayList<Float> data=new ArrayList<>();
        data.add(0.0f);             //设置圆心坐标
        data.add(0.0f);
        data.add(0.0f);
        float angDegSpan=360f/n;
        for(float i=0;i<360+angDegSpan;i+=angDegSpan){
            data.add((float) (radius*Math.sin(i*Math.PI/180f)));
            data.add((float)(radius*Math.cos(i*Math.PI/180f)));
            data.add(0.0f);
        }
        float[] f=new float[data.size()];
        for (int i=0;i<f.length;i++){
            f[i]=data.get(i);
        }

        circlePosition = f;

        //处理各个顶点的颜色
        color = new float[f.length*4/3];
        ArrayList<Float> tempC = new ArrayList<>();
        ArrayList<Float> totalC = new ArrayList<>();
        tempC.add(1.0f);
        tempC.add(0.0f);
        tempC.add(0.0f);
        tempC.add(1.0f);
        for (int i=0;i<f.length/3;i++){
            totalC.addAll(tempC);
        }

        for (int i=0; i<totalC.size();i++){
            color[i]=totalC.get(i);
        }
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        handleProgram(MyApplication.getInstance(), R.raw.iso_triangle_vertex_shader, R.raw.iso_triangle_fragment_shader);
        aPositionLocation = glGetAttribLocation("vPosition");
        aColorLocation = glGetAttribLocation("aColor");
        glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, colorBuffer);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glViewport(0, 0, width, height);
        orthoM("u_Matrix", width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        glClear(GLES30.GL_COLOR_BUFFER_BIT);
        glEnableVertexAttribArray(aPositionLocation);
        glEnableVertexAttribArray(aColorLocation);
        glDrawArrays(GLES30.GL_TRIANGLE_FAN, 0, circlePosition.length / POSITION_COMPONENT_COUNT);
        glDisableVertexAttribArray(aPositionLocation);
        glDisableVertexAttribArray(aColorLocation);
    }
}

OpenGL支持的图元

image



results matching ""

    No results matching ""